/**
 * The MIT License (MIT)
 * Copyright (c) 2021 铭软科技(mingsoft.net)
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


package net.mingsoft.basic.action.web;

import agw.service.gm.client.GMServiceClient;
import agw.service.gm.client.GMServiceStub;
import agw.service.gm.client.VerifySDReqI;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.mingsoft.base.entity.ResultData;
import net.mingsoft.basic.action.BaseAction;
import net.mingsoft.basic.biz.IAppBiz;
import net.mingsoft.basic.constant.e.SessionConstEnum;
import net.mingsoft.basic.entity.ManagerEntity;
import net.mingsoft.basic.strategy.ILoginStrategy;
import net.mingsoft.basic.util.AGWUtils;
import net.mingsoft.basic.util.BasicUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

/**
 *
 * @ClassName: LoginAction
 * @Description:TODO(登录的基础应用层)
 * @author: 铭飞开发团队
 * @date: 2015年1月27日 下午3:21:47
 *
 * @Copyright: 2018 www.mingsoft.net Inc. All rights reserved.
 */
@Api("登录的基础应用层接口")
@Controller
@RequestMapping("/${ms.manager.path}")
@Slf4j
public class LoginAction extends BaseAction {

    @Value("${ms.manager.path}")
    private String managerPath;

    @Value("${mock.skip.captcha:false}")
    private boolean isSkipCaptcha;

    @Resource
    ConcurrentMapCacheManager cacheManager;

    /**
     * 站点业务层
     */
    @Autowired
    private IAppBiz appBiz;

    @Autowired
    private ILoginStrategy loginStrategy;

    /**
     * 加载管理员登录界面
     *
     * @param request
     *            请求对象
     * @return 管理员登录界面地址
     */
    @ApiOperation(value = "加载管理员登录界面")
    @SuppressWarnings("resource")
    @GetMapping("/login")
    public String login(HttpServletRequest request) {
        Subject currentSubject = SecurityUtils.getSubject();
        ManagerEntity user = (ManagerEntity) currentSubject.getPrincipal();
        if (user != null && currentSubject.isAuthenticated()) {
            return "redirect:" + managerPath + "/index.do";
        }
        request.setAttribute("app", BasicUtil.getApp());
        return "/login";
    }

    /**
     * 验证登录
     *
     * @param manager
     *            管理员实体
     * @param request
     *            请求
     * @param response
     *            响应
     */
    @ApiOperation(value = "验证登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "managerName", value = "帐号", required = true, paramType = "query"),
            @ApiImplicitParam(name = "managerPassword", value = "密码", required = true, paramType = "query"),
    })
    @PostMapping("/login")
    @ResponseBody
    public ResultData login(@ModelAttribute @ApiIgnore ManagerEntity manager, HttpServletRequest request, HttpServletResponse response) {
        LOG.debug("basic checkLogin");

        if (manager.getLoginTag() != null && manager.getLoginTag() == 0) {
            if (!verifySignedData(manager)) {
                return ResultData.build().error("usbkey校验失败");
            }
        }

        // 检查账号是否已锁定
        if (isUserLocked(manager)) {
            return ResultData.build().error("帐号密码登陆失败超过三次，请5分钟后重试");
        }

        // 是否跳过验证码
        if (!isSkipCaptcha) {
            //验证
            if (!(checkRandCode())) {
                return ResultData.build().error(getResString("err.error", this.getResString("rand.code")));
            }
        }

        // 尝试登录
        if(loginStrategy.login(manager)){
            clearAllLock(manager);
            return ResultData.build().success();
        }else {
            // 尝试登录失败后，登录失败计数加1，累计为3次时，会锁定登录五分钟
            if (tryLockUser(manager)) {
                return ResultData.build().error("帐号密码登陆失败超过三次，请5分钟后重试");
            }
            return ResultData.build().error(getResString("err.error", this.getResString("manager.name.or.password")));
        }

    }

    /**
     * 检查用户是否锁定
     * @param manager 用户
     * @return 是否锁定
     */
    public boolean isUserLocked(ManagerEntity manager) {
        Cache cache = cacheManager.getCache("login-retry-times");
        if (cache != null) {
            // 判断用户是否已锁定：到期时间 <= now()
            Cache.ValueWrapper expiredWrapper = cache.get(manager.getManagerName() + "-expired");
            if (expiredWrapper != null) {
                DateTime expired = (DateTime) expiredWrapper.get();

                // 比对过期时间，当前时间在过期时间前，还未过期，账号仍然处于锁定状态
                if (DateTime.now().isBefore(expired)) {
                    return true;
                } else {
                    // 当前时间在过期时间后，则锁定时间到了，需要解锁已解锁
                    cache.evict(manager.getManagerName() + "-expired");
                    cache.evict(manager.getManagerName() + "-times");
                    return false;
                }
            }
        }
        // 用户未锁定
        return false;
    }

    /**
     * 尝试锁定用户
     * @param manager 用户
     */
    public boolean tryLockUser(ManagerEntity manager) {
        Cache cache = cacheManager.getCache("login-retry-times");
        if (cache != null) {
            Cache.ValueWrapper expiredWrapper = cache.get(manager.getManagerName() + "-times");

            if (expiredWrapper != null) {
                // 非初次尝试登录且失败
                Integer times = (Integer) expiredWrapper.get();
                if (times != null) {
                    times += 1;
                    // 错误次数累计超过三次则锁定用户
                    cache.put(manager.getManagerName() + "-times", times);
                    if (times >= 3) {
                        // 锁定五分钟
                        cache.put(manager.getManagerName() + "-expired", DateTime.now().plusMinutes(5));
                        return true;
                    }
                }
            } else {
                // 初次尝试登录且失败
                cache.put(manager.getManagerName() + "-times", 1);
            }
        } else {
            LOG.error("create cache failed.");
        }
        return false;
    }

    /**
     * 清理所有锁
     * @param manager 用户
     */
    public void clearAllLock(ManagerEntity manager) {
        Cache cache = cacheManager.getCache("login-retry-times");
        if (cache != null) {
            cache.evict(manager.getManagerName() + "-expired");
            cache.evict(manager.getManagerName() + "-times");
        }
    }

    @ApiModel(value="登录对象", description="登录对象")
    @Data
    static class SysLoginModel {
        @ApiModelProperty(value = "usbkey加密参数signedDataB64")
        private String signedDataB64;
        @ApiModelProperty(value = "usbkey加密签名userSignCert")
        private String userSignCert;
        @ApiModelProperty(value = "usbkey加密随机数")
        private String randomData;
        @ApiModelProperty(value = "登录是否加密判断")
        private Integer loginTag;
    }

    public boolean verifySignedData(ManagerEntity sysLoginModel) {
        GMServiceClient client = AGWUtils.getClient();
        VerifySDReqI vfSignReq = new VerifySDReqI();
        vfSignReq.setAppID(AGWUtils.getAppid());
        vfSignReq.setOperID(AGWUtils.VERIFY_SIGN);
        try {
            vfSignReq.setOrgData(sysLoginModel.getRandomData().getBytes(StandardCharsets.UTF_8));
            vfSignReq.setSignerCert(sysLoginModel.getUserSignCert());
            vfSignReq.setSignedData(sysLoginModel.getSignedDataB64());
            log.info("tdg|AgwServiceImpl|verifySignedData|req={}", JSONObject.toJSON(vfSignReq));
            GMServiceStub.VerifySDResp vfSignResp = client.verifySignedData(vfSignReq);
            log.info("tdg|AgwServiceImpl|verifySignedData|resp={}", JSONObject.toJSON(vfSignResp));
            String sException = "";
            if (vfSignResp.getResult() != 0) {
                sException = this.getClass().getName() + "-->verifySignedData fail:" + vfSignResp.getResult();
                log.error("tdg|AgwServiceImpl|verifySignedData|getResult!=0|message={}", sException);
                throw new RuntimeException(sException);
            }
        } catch (Exception e) {
            log.error("tdg|AgwServiceImpl|verifySignedData|签名验签异常|error={}", e.getMessage());
            throw new RuntimeException("签名验签异常");
        }
        return true;
    }
}
